﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Shapes;
using System.Xml.Schema;
using HelixToolkit.Wpf;
using IMPression;

namespace IMPressive
{
    /// <summary>
    /// Logique d'interaction pour GraphWindow.xaml
    /// </summary>
    public partial class Graph3DWindow : Window
    {
        private EquationParser parser = new EquationParser();

        public Graph3DWindow()
        {
            InitializeComponent();
            txtOperation.ItemsSource = Funcs;
        }

        private void txtOperation_KeyUp(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                Graph();
            }
        }

        public List<string> Funcs
        {
            get
            {
                var st = new List<string>() {"x", "z"};
                Function.FunctionsList.All(x =>
                {
                    st.Add(x.Contains('(') ? x.Substring(0, x.IndexOf('(')) : x);
                    return true;
                });
                st = st.OrderBy(x => x).Distinct().ToList();
                return st;
            }
        }

        private void Graph()
        {
            txtOperation.Text = parser.CleanUp(txtOperation.Text);

            xmin = parser.Calculate(txtFromX.Text);
            xmax = parser.Calculate(txtToX.Text);
            dx = parser.Calculate(txtPasX.Text);
            ymin = parser.Calculate(txtFromY.Text);
            ymax = parser.Calculate(txtToY.Text);
            dy = parser.Calculate(txtPasY.Text);
            texture_xscale = xmax - xmin;
            texture_yscale = ymax - ymin;

            DefineModel(MainModel3Dgroup);
        }


        private void btnLoad_Click(object sender, RoutedEventArgs e)
        {
            Graph();
        }


        private void btnClear_Click(object sender, RoutedEventArgs e)
        {
            for (int i = MainModel3Dgroup.Children.Count - 1; i >= 0; i--)
            {
                if (MainModel3Dgroup.Children[i] is GeometryModel3D)
                    MainModel3Dgroup.Children.RemoveAt(i);
            }
            PointDictionary.Clear();
            this.Title = "";
            center();
        }

        private ModelVisual3D model_visual = new ModelVisual3D();

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            
            center();

            DefineLights();

            model_visual = new ModelVisual3D();
            model_visual.Content = MainModel3Dgroup;

            //mainViewport.Children.Clear();
            mainViewport.Children.Add(model_visual);

            //tr.EventSource = mainViewport;

            
        }

        void center()
        {
            camera.Position = new Point3D(5, 30, 40);
            camera.LookDirection = new Vector3D(-5, -30, -40);
            camera.UpDirection = new Vector3D(0, 0, 1);
            ((PerspectiveCamera) camera).FieldOfView = 45;
        }

        double xmin = -10;
        double xmax = 10;
        double dx = 0.1;
        double ymin = -10;
        double ymax = 10;
        double dy = 0.1;

        private double texture_xscale = 20;
        private double texture_yscale = 20;

        private void DefineLights()
        {
            AmbientLight ambient_light = new AmbientLight(Colors.Gray);
            DirectionalLight directional_light =
                new DirectionalLight(Colors.Gray,
                    new Vector3D(-1.0, -3.0, -2.0));
            MainModel3Dgroup.Children.Add(ambient_light);
            MainModel3Dgroup.Children.Add(directional_light);
        }


        private int AddPoint(Point3DCollection points, PointCollection texture_coords, Point3D point)
        {
            if (PointDictionary.ContainsKey(point))
                return PointDictionary[point];
            points.Add(point);
            PointDictionary.Add(point, points.Count - 1);


            texture_coords.Add(
        new Point(
            (point.X - xmin) * texture_xscale,
            (point.Y - ymin) * texture_yscale));


            return points.Count - 1;
        }

        private GeometryModel3D surface_model =
            new GeometryModel3D();

        private GeometryModel3D wireframe_model =
            new GeometryModel3D();

        

        private void DefineModel(Model3DGroup model_group)
        {
            center();

            var st = new Stopwatch();
            st.Start();

            MeshGeometry3D mesh = new MeshGeometry3D();

            //mesh.TextureCoordinates = new PointCollection() { new Point(0, 0), new Point(0, 512), new Point(512, 512), new Point(512, 0) };

            for (double x = xmin; x <= xmax - dx; x += dx)
            {
                for (double y = ymin; y <= ymax - dy; y += dy)
                {
                    Point3D p00 = new Point3D(x, y, F(x, y));
                    Point3D p10 = new Point3D(x + dx, y, F(x + dx, y));
                    Point3D p01 = new Point3D(x, y + dx, F(x, y + dy));
                    Point3D p11 =
                        new Point3D(x + dx, y + dx, F(x + dx, y + dx));
                    AddTriangle(mesh, p00, p01, p11);
                    AddTriangle(mesh, p00, p11, p10);
                }
            }


            //var surface_material = new DiffuseMaterial(new SolidColorBrush(cpxColor.SelectedColor));

            CreateAltitudeMap();

            var surface_material = new DiffuseMaterial(new ImageBrush(new BitmapImage(new Uri("Texture" + txtOperation.Text.GetHashCode() + ".png", UriKind.Relative))));


            //mesh.TextureCoordinates = new PointCollection() { new Point(0, 0), new Point(0, 1), new Point(1, 1), new Point(1, 0) };
            surface_model = new GeometryModel3D(mesh, surface_material);
            surface_model.BackMaterial = surface_material;

            
            //mesh.TextureCoordinates = new PointCollection() { new Point(0, 0), new Point(0, 1), new Point(1, 1), new Point(1, 0) };

            model_group.Children.Add(surface_model);

            

            st.Stop();
            this.Title = txtOperation.Text + " - " + ((double) st.ElapsedMilliseconds / 1000.0) + " s";
        }

        private Dictionary<Point3D, int> PointDictionary = new Dictionary<Point3D, int>();

        private double F(double x, double y)
        {
            try
            {
                return parser.Calculate(txtOperation.Text, "x=" + x.ToString() + ";y=" + y.ToString());
            }
            catch (Exception ex)
            {
                return 0;
            }
        }
      

        #region Wireframe

        public MeshGeometry3D ToWireframe(MeshGeometry3D mesh, double thickness)
        {
            if (mesh.Bounds == Rect3D.Empty) return null;
            Dictionary<int, int> already_drawn = new Dictionary<int, int>();
            MeshGeometry3D wireframe = new MeshGeometry3D();
            for (int triangle = 0; triangle < mesh.TriangleIndices.Count; triangle += 3)
            {
                int index1 = mesh.TriangleIndices[triangle];
                int index2 = mesh.TriangleIndices[triangle + 1];
                int index3 = mesh.TriangleIndices[triangle + 2];

                AddTriangleSegment(mesh, wireframe, already_drawn, index1, index2, thickness);
                AddTriangleSegment(mesh, wireframe, already_drawn, index2, index3, thickness);
                AddTriangleSegment(mesh, wireframe, already_drawn, index3, index1, thickness);
            }

            return wireframe;
        }

        private void AddTriangleSegment(MeshGeometry3D mesh,
            MeshGeometry3D wireframe, Dictionary<int, int> already_drawn,
            int index1, int index2, double thickness)
        {
            if (index1 > index2)
            {
                int temp = index1;
                index1 = index2;
                index2 = temp;
            }
            int segment_id = index1 * mesh.Positions.Count + index2;

            if (already_drawn.ContainsKey(segment_id)) return;
            already_drawn.Add(segment_id, segment_id);

            AddSegment(wireframe, mesh.Positions[index1], mesh.Positions[index2], thickness);
        }

        public void AddSegment(MeshGeometry3D mesh,
            Point3D point1, Point3D point2, double thickness, bool extend)
        {
            Vector3D up = new Vector3D(0, 1, 0);
            Vector3D segment = point2 - point1;
            segment.Normalize();
            if (Math.Abs(Vector3D.DotProduct(up, segment)) > 0.9)
                up = new Vector3D(1, 0, 0);

            AddSegment(mesh, point1, point2, up, thickness, extend);
        }

        public void AddSegment(MeshGeometry3D mesh,
            Point3D point1, Point3D point2, double thickness)
        {
            AddSegment(mesh, point1, point2, thickness, false);
        }

        public void AddSegment(MeshGeometry3D mesh,
            Point3D point1, Point3D point2, Vector3D up, double thickness)
        {
            AddSegment(mesh, point1, point2, up, thickness, false);
        }

        public void AddSegment(MeshGeometry3D mesh,
            Point3D point1, Point3D point2, Vector3D up, double thickness,
            bool extend)
        {
            Vector3D v = point2 - point1;

            if (extend)
            {
                Vector3D n = ScaleVector(v, thickness / 2.0);
                point1 -= n;
                point2 += n;
            }

            Vector3D n1 = ScaleVector(up, thickness / 2.0);

            Vector3D n2 = Vector3D.CrossProduct(v, n1);
            n2 = ScaleVector(n2, thickness / 2.0);

            Point3D p1pp = point1 + n1 + n2;
            Point3D p1mp = point1 - n1 + n2;
            Point3D p1pm = point1 + n1 - n2;
            Point3D p1mm = point1 - n1 - n2;
            Point3D p2pp = point2 + n1 + n2;
            Point3D p2mp = point2 - n1 + n2;
            Point3D p2pm = point2 + n1 - n2;
            Point3D p2mm = point2 - n1 - n2;

            AddTriangle(mesh, p1pp, p1mp, p2mp);
            AddTriangle(mesh, p1pp, p2mp, p2pp);

            AddTriangle(mesh, p1pp, p2pp, p2pm);
            AddTriangle(mesh, p1pp, p2pm, p1pm);

            AddTriangle(mesh, p1pm, p2pm, p2mm);
            AddTriangle(mesh, p1pm, p2mm, p1mm);

            AddTriangle(mesh, p1mm, p2mm, p2mp);
            AddTriangle(mesh, p1mm, p2mp, p1mp);

            AddTriangle(mesh, p1pp, p1pm, p1mm);
            AddTriangle(mesh, p1pp, p1mm, p1mp);

            AddTriangle(mesh, p2pp, p2mp, p2mm);
            AddTriangle(mesh, p2pp, p2mm, p2pm);
        }

        public Vector3D ScaleVector(Vector3D vector, double length)
        {
            double scale = length / vector.Length;
            return new Vector3D(
                vector.X * scale,
                vector.Y * scale,
                vector.Z * scale);
        }

        #endregion

        private void AddTriangle(MeshGeometry3D mesh,
            Point3D point1, Point3D point2, Point3D point3)
        {
            int index1 = AddPoint(mesh.Positions, mesh.TextureCoordinates, point1);
            int index2 = AddPoint(mesh.Positions, mesh.TextureCoordinates, point2);
            int index3 = AddPoint(mesh.Positions, mesh.TextureCoordinates, point3);

            mesh.TriangleIndices.Add(index1);
            mesh.TriangleIndices.Add(index2);
            mesh.TriangleIndices.Add(index3);
        }

        private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
        {
        }

        private Model3DGroup MainModel3Dgroup = new Model3DGroup();

        private PerspectiveCamera TheCamera;


        private void CreateAltitudeMap()
        {
            if (File.Exists("Texture" + txtOperation.Text.GetHashCode() + ".png")) return;

            // Calculate the function's value over the area.
            int xwidth = 512;
            int ywidth = 512;
            double dx = (xmax - xmin) / xwidth;
            double dy = (ymax - ymin) / ywidth;
            double[,] values = new double[xwidth, ywidth];
            for (int ix = 0; ix < xwidth; ix++)
            {
                double x = xmin + ix * dx;
                for (int iy = 0; iy < ywidth; iy++)
                {
                    double y = ymin + iy * dy;
                    values[ix, iy] = F(x, y);
                }
            }

            // Get the upper and lower bounds on the values.
            var get_values =
                from double value in values
                select value;
            double zmin = get_values.Min();
            double zmax = get_values.Max();

            // Make the BitmapPixelMaker.
            BitmapPixelMaker bm_maker = new BitmapPixelMaker(xwidth, ywidth);

            // Set the pixel colors.
            for (int ix = 0; ix < xwidth; ix++)
            {
                for (int iz = 0; iz < ywidth; iz++)
                {
                    byte red, green, blue;
                    MapRainbowColor(values[ix, iz], zmin, zmax,
                        out red, out green, out blue);
                    bm_maker.SetPixel(ix, iz, red, green, blue, 255);
                }
            }

            // Convert the BitmapPixelMaker into a WriteableBitmap.
            WriteableBitmap wbitmap = bm_maker.MakeBitmap(96, 96);

            //if (File.Exists("Texture" + txtOperation.Text.GetHashCode() + ".png")) File.Delete("Texture.png");
            // Save the bitmap into a file.
            wbitmap.Save("Texture" + txtOperation.Text.GetHashCode() + ".png");
        }
        private void MapRainbowColor(double value, double min_value, double max_value,
            out byte red, out byte green, out byte blue)
        {
            // Convert into a value between 0 and 1023.
            int int_value = (int)(1023 * (value - min_value) / (max_value - min_value));

            // Map different color bands.
            if (int_value < 256)
            {
                // Red to yellow. (255, 0, 0) to (255, 255, 0).
                red = 255;
                green = (byte)int_value;
                blue = 0;
            }
            else if (int_value < 512)
            {
                // Yellow to green. (255, 255, 0) to (0, 255, 0).
                int_value -= 256;
                red = (byte)(255 - int_value);
                green = 255;
                blue = 0;
            }
            else if (int_value < 768)
            {
                // Green to aqua. (0, 255, 0) to (0, 255, 255).
                int_value -= 512;
                red = 0;
                green = 255;
                blue = (byte)int_value;
            }
            else
            {
                // Aqua to blue. (0, 255, 255) to (0, 0, 255).
                int_value -= 768;
                red = 0;
                green = (byte)(255 - int_value);
                blue = 255;
            }
        }
        


        private string osi = "";
        private void txtOperation_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (osi == (txtOperation.SelectedItem == null ? "" : txtOperation.SelectedItem.ToString())) return;
            if (osi != txtOperation.Text) return;
            if (txtOperation.IsDropDownOpen) return;

            if (txtOperation.Text.Contains('('))
            {
                txtOperation.Text = txtOperation.Text.Substring(0, txtOperation.Text.IndexOf('('));
            }
        }

        private void txtOperation_DropDownClosed(object sender, RoutedPropertyChangedEventArgs<bool> e)
        {
            if (txtOperation.SelectedItem == null) return;
            osi = txtOperation.SelectedItem.ToString();
        }

        private void mainViewport_CameraChanged(object sender, RoutedEventArgs e)
        {
            /*if (TheCamera == null) return;
            this.Title = TheCamera.LookDirection.ToString() + " ; " + TheCamera.UpDirection.ToString() + " ; " +
                         TheCamera.Position.ToString();*/
        }

        
    }

   
}